<?php
class DynamoDB {
	public $itemsProcessed = null;
	public $itemsUnprocessed = null;
	public $readCapacityUnits = 10;
	public $writeCapacityUnits = 5;
	public $lastResponse = null;
	
	private $db = null;
	
	public function __construct($opts = null) {
		$this->db = new AmazonDynamoDB();
		if (!is_null($opts) && is_array($opts)) {
			foreach ($opts as $key => $value) {
				switch ($key) {
					case "readCapacityUnits":
						$this->readCapacityUnits = $value;
						break;
					case "writeCapacityUnits":
						$this->writeCapacityUnits = $value;
						break;
					default:
						throw new Exception ("Unknown key in options: '$key'");
						break;
				}
			}
		}
	}
	
	private function __catchError($res) {
		$rawMessage = (string) $res->body->message;
		$rawType = (string) $res->body->__type;
		$rawStatus = (int) $res->status;
		if ($rawStatus == 413) {
			return (array(
				"code" => 413, 
				"error" => "Request Entity Too Large", 
				"message" => "Maximum item size of 1MB exceeded.", 
				"retry" => false, 
				"rawStatus" => $rawStatus,
				"rawMessage" => $rawMessage,
				"rawType" => $rawType
			));
		}
		$errorTypeParts = explode("#", $rawType);
		if (isset($errorTypeParts[1])) {
			switch ($errorTypeParts[1]) {
				case "AccessDeniedException":
					$code = 400;
					$error = "Access denied";
					$message = "General authentication failure. The client did not correctly sign the request. Consult the signing documentation.";
					$retry = false;
					break;
				case "ConditionalCheckFailedException":
					$code = 400;
					$error = "The conditional request failed.";
					$message = "Example: The expected value did not match what was stored in the system.";
					$retry = false;
					break;
				case "IncompleteSignatureException":
					$code = 400;
					$error = "The request signature does not conform to AWS standards.";
					$message = "The signature in the request did not include all of the required components. See Calculating the HMAC-SHA256 Signature for Amazon DynamoDB.";
					$retry = false;
					break;
				case "LimitExceededException":
					$code = 400;
					$error = "Too many operations for a given subscriber.";
					$message = "Example: The number of concurrent table requests (cumulative number of tables in the CREATING, DELETING or UPDATING state) exceeds the maximum allowed of 20. The total limit of tables (currently in the ACTIVE state) is 250.";
					$retry = false;
					break;
				case "MissingAuthenticationTokenException":
					$code = 400;
					$error = "Too many operations for a given subscriber.";
					$message = "The request did not include the required x-amz-security-token. See Making HTTP Requests to Amazon DynamoDB.";
					$retry = false;					
					break;
				case "ProvisionedThroughputExceededException":
					$code = 400;
					$error = "You exceeded your maximum allowed provisioned throughput.";
					$message = "Example: Your request rate is too high or the request is too large. The AWS SDKs for Amazon DynamoDB automatically retry requests that receive this exception. So, your request is eventually successful, unless the request is too large or your retry queue is too large to finish. Reduce the frequency of requests, using Error Retries and Exponential Backoff. Or, see Specifying Read and Write Requirements (Provisioned Throughput) for other strategies.";
					$retry = true;
					break;
				case "ResourceInUseException":
					$code = 400;
					$error = "The resource which is being attempted to be changed is in use.";
					$message = "Example: You attempted to recreate an existing table, or delete a table currently in the CREATING state.";
					$retry = false;
					break;
				case "ResourceNotFoundException":
					$code = 400;
					$error = "The resource which is being requested does not exist.";
					$message = "Example: Table which is being requested does not exist, or is too early in the CREATING state.";
					$retry = false;					
					break;
				case "ThrottlingException":
					$code = 400;
					$error = "Rate of requests exceeds the allowed throughput.";
					$message = "This can be returned by the control plane API (CreateTable, DescribeTable, etc) when they are requested too rapidly.";
					$retry = true;
					break;
				case "ValidationException":
					$code = 400;
					$error = "One or more required parameter values were missing.";
					$message = "One or more required parameter values were missing.";
					$retry = false;
					break;
				case "InternalFailure":
					$code = 500;
					$error = "The server encountered an internal error trying to fulfill the request.";
					$message = "The server encountered an error while processing your request.";
					$retry = true;
					break;
				case "InternalServerError":
					$code = 500;
					$error = "The server encountered an internal error trying to fulfill the request.";
					$message = "The server encountered an error while processing your request.";
					$retry = true;
					break;
				case "ServiceUnavailableException":
					$code = 500;
					$error = "The service is currently unavailable or busy.";
					$message = "There was an unexpected error on the server while processing your request.";
					$retry = true;
					break;
				default:
					$code = 999;
					$error = "Unknown Error.";
					$message = "Unknow Error; see raw results.";
					$retry = false;
					break;
			}
		} else {
			$code = 999;
			$error = "Unknown Error.";
			$message = "Unknow Error; see raw results.";
			$retry = false;
		}
		return (array(
				"code" => $code, 
				"error" => $error, 
				"message" => $message, 
				"retry" => $retry,
				"rawStatus" => $rawStatus,
				"rawMessage" => $rawMessage,
				"rawType" => $rawType));
	}
	
	public function __listTables() {
		$res = $this->db->list_tables();
		$this->lastResponse = array("timestamp" => time(), "response" => $res);
		if ($res->isOK()) {
			$names = array();
			foreach($res->body->TableNames as $name) {
				$names[] = (string) $name;
			}
			return($names);
		} else {
			throw new Exception("Unable to fetch list");
		}
	}
	
	private function __createTable($tableName, $schema) {
		if ($this->tableExist($tableName)) {
			throw new Exception("Table '$tableName' already exists");
		}
		$res = $this->db->create_table(array(
			"TableName" => $tableName,
			"KeySchema" => $schema,
			"ProvisionedThroughput" => array("ReadCapacityUnits" => $this->readCapacityUnits, "WriteCapacityUnits" => $this->writeCapacityUnits)
		));
		
		$this->lastResponse = array("timestamp" => time(), "response" => $res);
		
		if (!$res->isOK()) {
			$error = $this->__catchError($res);
			throw new Exception($error["error"]);
		}
		echo "Creating table";
		while (!$this->__isTableActive($tableName)) {
			echo ".";
			sleep(1);
		}
		echo "\n";
		return true;
	}
	
	public function deleteTable($tableName) {
		$res = $this->db->delete_table(array("TableName" => $tableName));
		$this->lastResponse = array("timestamp" => time(), "response" => $res);
		if ($res->isOK()) {
			echo "Deleting table";
			while($this->__getTableStatus($tableName) !== false) {
				echo ".";
				sleep (1);
			}
			echo "\n";
			return true;
		} else {
			$error = $this->__catchError($res);
			throw new Exception($error["error"]);
		}
	}

	private function __isTableActive($tableName) {
		$res = $this->db->describe_table(array("TableName" => $tableName));
		if ((string) $res->body->Table->TableStatus == 'ACTIVE') {
			return true;
		}
		return false;
	}
	
	private function __getTableStatus($tableName) {
		$res = $this->db->describe_table(array("TableName" => $tableName));
		if (isset($res->body->Table->TableStatus)) {
			return ((string) $res->body->Table->TableStatus);
		}
		return false;
	}
	
	public function tableExist($tableName) {
		$res = $this->db->describe_table(array("TableName" => $tableName));
		if (strpos((string) $res->body->__type, "ResourceNotFoundException") !== false) {
			return false;
		}
		return true;
	}
	
	public function createMasterListTable($masterListName) {
		$schema = array(
			'HashKeyElement' => array(
				'AttributeName' => 'objectName',
				'AttributeType' => AmazonDynamoDB::TYPE_STRING
			),
			'RangeKeyElement' => array(
				'AttributeName' => 'versionID',
				'AttributeType' => AmazonDynamoDB::TYPE_STRING
			)
		);
		return ($this->__createTable($masterListName, $schema));
	}
	
	public function batchInsert($tableName, $puts) {
		$queue = new CFBatchRequest();
		$queue->use_credentials($this->db->credentials);
		
		foreach ($puts as $item) {
			$this->db->batch($queue)->put_item(array('TableName' => $tableName, "Item" => $item));
		}
		$res = $this->db->batch($queue)->send();
		$this->lastResponse = $res;
		if ($res->areOK()) {
			return true;
		} else {
			print_r($puts);
			throw new Exception("Error in batchInsert()... see response");
		}
	}	
	
	/*
	public function batchInsert($tableName, $puts) {
		$this->itemsProcessed = null;
		$this->itemsUnprocessed = null;
		
		$items = array();
		$i = 0;		
		foreach ($puts as $item) {
			$items[] = array('PutRequest' => array('Item' => $this->db->attributes($item)));
			if ($i++ == 24) {
				$batches[] = array(
					'RequestItems' => array(
						$tableName => $items
				));
				$items = array();
				$i = 0;
			}
		}
		$batches[] = array(
			'RequestItems' => array(
				$tableName => $items
		));

		foreach ($batches as $batch) {
			$res = $this->db->batch_write_item ($batch);
			$this->lastResponse = array("timestamp" => time(), "response" => $res);
			if (!$res->isOK()) {
				echo "ERROR\n";
				var_dump($res->body);
			} else {
				$this->itemsProcessed += (int) $res->body->Responses->$tableName->ConsumedCapacityUnits;
				$this->itemsUnprocessed += count((array) $res->body->UnprocessedItems->$tableName);
				var_dump((array) $res->body->UnprocessedItems->$tableName->$tableName);
				
			}
		}
	}
	*/
	
	public function getItem($tableName, $hash, $range, $attrs) {
		$res = $this->db->get_item(array(
			"TableName" => $tableName,
			"Key" => $this->db->attributes(array(
				"HashKeyElement" => $hash,
				"RangeKeyElement" => $range)),
			"AttributesToGet" => $attrs,
			"ConsistentRead" => true
		));
		$this->lastResponse = array("timestamp" => time(), "response" => $res);
		if ($res->isOK()) {
			echo "SUCCESS\n";
			var_dump($res);
		} else {
			echo "ERROR\n";
			var_dump($res);
		}
	}
	
	public function dumpTable($tableName) {
		$res = $this->db->scan(array("TableName" => $tableName));
		$this->lastResponse = array("timestamp" => time(), "response" => $res);
		var_dump($res);
	}
}
?>